package net.lindeer.lang

import java.lang.RuntimeException

internal class Parser(private val tokens: List<Token>) {
    private var current = 0

    private fun match(vararg types: Token.Type): Boolean {
        val cur = peek.type
        return cur != Token.Type.EOF && types.contains(cur)
    }

    private val isEnd: Boolean
        get() = peek.type == Token.Type.EOF

    private val peek: Token
        get() = tokens[current]

    private val advance: Token
        get() = tokens[current].apply {
            if (!isEnd) {
                current++
            }
        }

    private fun consume(type: Token.Type, message: String): Token {
        if (match(type)) {
            return advance
        } else {
            throw makeError(peek, message)
        }
    }

    private fun expression(): Expr {
        return assignment()
    }

    private fun assignment(): Expr {
        val expr = or()
        if (match(Token.Type.EQUAL)) {
            val assign = advance
            val right = assignment()
            if (expr is Expr.Variable) {
                val name = expr.name
                return Expr.Assign(name, right)
            } else if (expr is Expr.Getter) {
                return Expr.Setter(expr.who, expr.name, right)
            }
            Rox.error(assign, "Invalid assignment target")
        }
        return expr
    }

    private fun or(): Expr {
        var expr = and()
        while (match(Token.Type.OR)) {
            val operator = advance
            val right = and()
            expr = Expr.Logical(expr, operator, right)
        }
        return expr
    }

    private fun and(): Expr {
        var expr = equality()
        while (match(Token.Type.AND)) {
            val operator = advance
            val right = equality()
            expr = Expr.Logical(expr, operator, right)
        }
        return expr
    }

    private fun equality(): Expr {
        var expr = comparison()
        while (match(Token.Type.NOT_EQUAL, Token.Type.EQUAL_EQUAL)) {
            val operator = advance
            val right = comparison()
            expr = Expr.Binary(expr, operator, right)
        }
        return expr
    }

    private fun comparison(): Expr {
        var expr = term()
        while (match(Token.Type.GREATER, Token.Type.GREATER_EQUAL, Token.Type.LESS, Token.Type.LESS_EQUAL)) {
            val operator = advance
            val right = term()
            expr = Expr.Binary(expr, operator, right)
        }
        return expr
    }

    private fun term(): Expr {
        var expr = factor()
        while (match(Token.Type.MINUS, Token.Type.PLUS)) {
            val operator = advance
            val right = factor()
            expr = Expr.Binary(expr, operator, right)
        }
        return expr
    }

    private fun factor(): Expr {
        var expr = unary()
        while (match(Token.Type.SLASH, Token.Type.STAR)) {
            val operator = advance
            val right = unary()
            expr = Expr.Binary(expr, operator, right)
        }
        return expr
    }

    private fun unary(): Expr {
        return if (match(Token.Type.NOT, Token.Type.MINUS)) {
            val operator = advance
            val right = unary()
            Expr.Unary(operator, right)
        } else {
            call()
        }
    }

    private fun finishCall(callee: Expr): Expr {
        val arguments = mutableListOf<Expr>()
        if (peek.type != Token.Type.RIGHT_PAREN) {
            do {
                if (arguments.size > 254) {
                    Rox.error(peek, "Can't have more than 255 arguments!")
                }
                arguments.add(expression())
            } while ({ val isComma = match(Token.Type.COMMA); if (isComma) advance; isComma }())
        }
        val paren = consume(Token.Type.RIGHT_PAREN, "Expect ')' after arguments!")
        return Expr.Call(callee, paren, arguments)
    }

    private fun call(): Expr {
        var expr = primary()
        while (true) {
            if (match(Token.Type.LEFT_PAREN)) {
                advance
                expr = finishCall(expr)
            } else if (match(Token.Type.DOT)) {
                advance
                val name = consume(Token.Type.IDENTIFIER, "Expect property name after '.'")
                expr = Expr.Getter(expr, name)
            } else {
                break
            }
        }
        return expr
    }

    private fun primary(): Expr {
        return when {
            match(Token.Type.FALSE) -> {
                advance
                Expr.Literal(false)
            }
            match(Token.Type.TRUE) -> {
                advance
                Expr.Literal(true)
            }
            match(Token.Type.NULL) -> {
                advance
                Expr.Literal(null)
            }
            match(Token.Type.NUMBER, Token.Type.STRING) -> {
                val token = advance
                Expr.Literal(token.literal)
            }
            match(Token.Type.SUPER) -> {
                val keyword = advance
                consume(Token.Type.DOT, "Expect '.' after 'super'!")
                val method = consume(Token.Type.IDENTIFIER, "Expect superclass method name!")
                Expr.Super(keyword, method)
            }
            match(Token.Type.THIS) -> {
                val token = advance
                Expr.This(token)
            }
            match(Token.Type.IDENTIFIER) -> {
                val token = advance
                Expr.Variable(token)
            }
            match(Token.Type.LEFT_PAREN) -> {
                advance
                val expr = expression()
                consume(Token.Type.RIGHT_PAREN, "Expect ')' after expression!")
                return Expr.Grouping(expr)
            }
            else -> {
                throw makeError(peek, "Expect expression!")
            }
        }
    }

    // statement rules
    private fun statement(): Stmt {
        if (match(Token.Type.FOR)) {
            advance
            return forStatement()
        }
        if (match(Token.Type.IF)) {
            advance
            return ifStatement()
        }
        if (match(Token.Type.PRINT)) {
            advance
            return printStatement()
        }
        if (match(Token.Type.RETURN)) {
            val token = advance
            return returnStatement(token)
        }
        if (match(Token.Type.WHILE)) {
            advance
            return whileStatement()
        }
        if (match(Token.Type.LEFT_BRACE)) {
            advance
            return Stmt.Block(block())
        }

        return expressionStatement()
    }

    private fun forStatement(): Stmt {
        consume(Token.Type.LEFT_PAREN, "Expect '(' after 'for'!")
        val initializer = when {
            match(Token.Type.SEMICOLON) -> {
                advance
                null
            }
            match(Token.Type.VAR) -> {
                advance
                varDeclaration()
            }
            else -> {
                expressionStatement()
            }
        }
        val condition = if (peek.type != Token.Type.SEMICOLON) {
            expression()
        } else Expr.Literal(true)
        consume(Token.Type.SEMICOLON, "Expect ';' after for loop condition")
        val increment = if (peek.type != Token.Type.RIGHT_PAREN) {
            expression()
        } else null
        consume(Token.Type.RIGHT_PAREN, "Expect ')' after for clauses!")
        val body = statement().let {
            if (increment != null) {
                Stmt.Block(listOf(it, Stmt.Expression(increment)))
            } else {
                it
            }
        }
        return Stmt.While(condition, body).let {
            if (initializer != null) {
                Stmt.Block(listOf(initializer, it))
            } else it
        }
    }

    private fun expressionStatement(): Stmt {
        val expr = expression()
        consume(Token.Type.SEMICOLON, "Expect ';' after expression!")
        return Stmt.Expression(expr)
    }

    private fun printStatement(): Stmt {
        val value = expression()
        consume(Token.Type.SEMICOLON, "Expect ';' after value!")
        return Stmt.Print(value)
    }

    private fun returnStatement(keyword: Token): Stmt {
        val value = if (peek.type != Token.Type.SEMICOLON) {
            expression()
        } else null
        consume(Token.Type.SEMICOLON, "Expect ';' after return value!")
        return Stmt.Return(keyword, value)
    }

    private fun whileStatement(): Stmt {
        consume(Token.Type.LEFT_PAREN, "Expect '(' before 'while'!")
        val condition = expression()
        consume(Token.Type.RIGHT_PAREN, "Expect ')' after while condition!")
        val body = statement()
        return Stmt.While(condition, body)
    }

    private fun block(): List<Stmt> {
        val statements = mutableListOf<Stmt>()
        while ({ val type = peek.type; type != Token.Type.RIGHT_BRACE && type != Token.Type.EOF }()) {
            declaration()?.let { statements.add(it) }
        }
        consume(Token.Type.RIGHT_BRACE, "Expect '}' after block!")
        return statements
    }

    private fun ifStatement(): Stmt {
        consume(Token.Type.LEFT_PAREN, "Expect '(' after if!")
        val condition = expression()
        consume(Token.Type.RIGHT_PAREN, "Expect ')' after if condition!")
        val thenStmt = statement()
        val elseStmt = if (match(Token.Type.ELSE)) {
            advance
            statement()
        } else null
        return Stmt.If(condition, thenStmt, elseStmt)
    }

    private fun function(kind: String): Stmt.Func {
        val name = consume(Token.Type.IDENTIFIER, "Expect $kind name!")
        consume(Token.Type.LEFT_PAREN, "Expect '(' after $kind name!")
        val parameters = mutableListOf<Token>()
        if (!match(Token.Type.RIGHT_PAREN)) {
            do {
                if (parameters.size > 254) {
                    Rox.error(peek, "Can't have more than 255 parameters!")
                }
                parameters.add(consume(Token.Type.IDENTIFIER, "Expect parameter name"))
            } while ({ val isComma = match(Token.Type.COMMA); if (isComma) advance; isComma }())
        }
        consume(Token.Type.RIGHT_PAREN, "Expect ')' after parameters!")
        consume(Token.Type.LEFT_BRACE, "Expect '{' before $kind body!")
        val body = block()
        return Stmt.Func(name, parameters, body)
    }

    private fun classDeclaration(): Stmt {
        val name = consume(Token.Type.IDENTIFIER, "Expect valid class name!")
        val superclass = if (match(Token.Type.LESS)) {
            advance
            val clazz = consume(Token.Type.IDENTIFIER, "Expect super class name!")
            Expr.Variable(clazz)
        } else null
        consume(Token.Type.LEFT_BRACE, "Expect '{' before class body!")
        val functions = mutableListOf<Stmt.Func>()
        while (!isEnd && peek.type != Token.Type.RIGHT_BRACE) {
            functions.add(function("method"))
        }
        consume(Token.Type.RIGHT_BRACE, "Expect '}' after class body!")
        return Stmt.Clazz(name, superclass, functions)
    }

    private fun declaration(): Stmt? {
        return try {
            if (match(Token.Type.CLASS)) {
                advance
                return classDeclaration()
            }
            if (match(Token.Type.FUN)) {
                advance
                return function("function")
            }
            if (match(Token.Type.VAR)) {
                advance
                varDeclaration()
            } else {
                statement()
            }
        } catch (e: ParseError) {
            restore()
            null
        }
    }

    private fun varDeclaration(): Stmt {
        val name = consume(Token.Type.IDENTIFIER, "Expect variable name!")
        val initializer = if (match(Token.Type.EQUAL)) {
            advance
            expression()
        } else null
        consume(Token.Type.SEMICOLON, "Expect ';' after variable declaration!")
        return Stmt.Var(name, initializer)
    }

    private fun makeError(token: Token, message: String): ParseError {
        Rox.error(token, message)
        return ParseError()
    }

    fun parse(): List<Stmt> {
        val statements = mutableListOf<Stmt>()
        while (!isEnd) {
            declaration()?.apply { statements.add(this) }
        }
        return statements
    }

    private fun restore() {
        while (!isEnd) {
            val token = advance
            when (token.type) {
                Token.Type.SEMICOLON,
                Token.Type.CLASS,
                Token.Type.FUN,
                Token.Type.VAR,
                Token.Type.FOR,
                Token.Type.IF,
                Token.Type.WHILE,
                Token.Type.PRINT,
                Token.Type.RETURN -> return
                else -> {
                }
            }
        }
    }

    class ParseError : RuntimeException()
}
